/*
 * player_mgr.cpp
 *
 *  Created on: Oct 28, 2013
 *      Author: killerg
 */

#include "../server_deps.h"
#include "../server.h"
#include "../server_cfg.h"
#include "../server_console.h"

#include "player_mgr.h"
#include "player_data.h"
#include "game_cfg.h"


#include "share/util.h"

#include "share/db_mgr.h"
#include "share/clock.h"
#include "share/sl_msg.pb.h"
#include <atomic>

bool player_mgr::player_load_byname(const string& name, load_cb_t cb) {
    auto itr = name_table.find(name);
    if (itr != name_table.end()) {
        int64_t playerid = itr->second;
        if (playerid >= 0) {
        	return player_load(playerid, cb);
        }
    }
    return true;
}

bool player_mgr::player_load(int64_t playerid, load_cb_t cb) {
	if (!g_dbf->ok()) {
		if (cb)
			cb(NULL);
		return false;
	}

    auto stub = stubs.get(playerid);
    if (NULL == stub) {
        stub = new stub_t;
        stub->id = playerid;
        stubs.set(playerid, stub);
    }

    stub->load_stamp = g_clock->tt_now;
    if (stub->player) {
        cb(stub->player);
        return true;
    }

    if (!stub->load_cb_list.empty()) {
        stub->load_cb_list.push_back(cb);
        return true;
    }

    stub->load_cb_list.push_back(cb);
    bool res = g_dbf->get(
        fmt_cstr("player:%d:%d", playerid/co::MaxAccountNum, playerid%co::MaxAccountNum),
        [playerid](db_error_t error, const Json::Value &value){
            auto stub = g_playermgr->stubs.get(playerid);
            if (NULL == stub)
                return;

            if (NULL == stub->player) {
                if (DBE_Success == error) {
                    stub->player = make_shared<player_t>();
                    stub->player->init(value);
                }
            }

            while (!stub->load_cb_list.empty()) {
                auto cb = stub->load_cb_list.front();
                if (cb)
                    cb(stub->player);
                stub->load_cb_list.pop_front();
            }

            if (NULL == stub->player) {
                g_playermgr->stubs.erase(playerid);
                safe_del(stub);
            }
        }
    );

    return res;
}

bool player_mgr::player_create(int64_t playerid, const string &name, EPlayerSex sex, const string &account, conn_new_cb_t cb) {
	if (!g_dbf->ok()) {
		if (cb)
			cb(DBE_TryMoreLater);
		return false;
	}
    if (name_creating.count(name) > 0 || name_table.count(name) > 0) {
        if (cb)
            cb(DBE_TryMoreLater);
        return false;
    }

    auto stub = stubs.get(playerid);
    if (NULL != stub) {
        if (cb)
            cb(DBE_TryMoreLater);
        return false;
    }

    stub = new stub_t;
    stub->id = playerid;
    stub->create_cb = cb;
    stubs.set(stub->id, stub);

    player_t newpd;
    newpd.init(playerid, name, sex, account);

    for (int i=0; i<3; ++i) {
        int32_t itemprotoid = g_game_cfg->PlayerCreateGiveItem[i];
        if (itemprotoid >= 0 && MAProto(Item, itemprotoid) != NULL) {
            newpd.add_item(itemprotoid, 1);
        }
    }

    std::shared_ptr<simple_player_info_t> info = make_shared<simple_player_info_t>();
    info->playerid = newpd.id;
    info->name = newpd.att(Plss_Name);
    info->sex = (EPlayerSex)newpd.att(Pl4s_PlayerSex);
    info->total_combat_force = newpd.att(Pl4s_TotalCombatForce);
    info->medal = newpd.att(Pl4s_Medal);
    info->equip_level = 10;
    for (int i=0; i<PlayerEquipSlotNum; ++i) {
        if (info->equip_level > newpd.equip_bar[i].equip_level)
            info->equip_level = newpd.equip_bar[i].equip_level;
    }

    g_playermgr->name_creating.insert(info->name);

    Json::Value json;
    newpd.save(json);
    bool res = g_dbf->set(
            fmt_cstr("player:%d:%d", newpd.id/co::MaxAccountNum, newpd.id%co::MaxAccountNum),
            json,
            [info, playerid](db_error_t error){
                    g_playermgr->name_creating.erase(info->name);

                    auto stub = g_playermgr->stubs.get(playerid);
                    if (NULL == stub)
                        return;

                    if (error == DBE_Success) {
                        auto inf = new simple_player_info_t;
                        *inf = *info;
                        g_playermgr->infos.set(inf->playerid, inf);
                        g_playermgr->name_table[inf->name] = inf->playerid;
                    }

                    if (stub->create_cb) {
                        stub->create_cb(error);
                        stub->create_cb = NULL;
                    }

                    if (error != DBE_Success){
                        g_playermgr->stubs.erase(playerid);
                        safe_del(stub);
                    }
                });

    return res;
}

bool player_mgr::init() {
    stringstream ss;
    ss << "/_design/player/_view/simple_player_info?";
//    ss << "full_set=true";
    ss << "&stale=false";
    ss << "&startkey=[" << g_cfg->ServerId << "%2C" << 0 << "]";
    ss << "&endkey=[" << (g_cfg->ServerId + 1)<< "%2C" << "null" << "]";
    ss << "&connection_timeout=60000";
    bool res = g_dbs->view(
            ss.str().c_str(),
            [&res, this](db_error_t err, const Json::Value &value){
                if (DBE_Success != err) {
                    UTIL_LOG(E,
                            "player_mgr::init view_cb: %s\n",
                            db_strerror(err));
                    res = false;
                    return;
                }

                if (!value["error"].isNull()) {
                    UTIL_LOG(E, "player_mgr::init view_result %s", value["reason"].asCString());
                    res = false;
                    return;
                }
                const Json::Value &rows = value["rows"];
                for (auto itr = rows.begin(); itr != rows.end(); ++itr) {
                    const Json::Value &value = (*itr)["value"];
                    int64_t id = value["id"].asInt64() + value["serverid"].asInt64() * co::MaxAccountNum;

                    auto ptr = infos.get(id);
                    if (NULL != ptr) {
                        UTIL_LOG(E, "player_mgr::init view_result player id:%d exist", id);
                        res = false;
                        continue;
                    }

                    ptr = new simple_player_info_t;

                    ptr->playerid = id;
                    ptr->name = value.get("name", "").asCString();
                    ptr->sex = (EPlayerSex)value.get("sex", -1).asInt();
                    ptr->total_combat_force = value.get("total_combat_force", 0).asInt();
                    ptr->medal = value.get("medal", 1).asInt();
                    ptr->equip_level = value.get("equip_level", 1).asInt();

                    infos.set(id, ptr);
                    name_table[ptr->name] = id;
                }
            });
    g_dbs->wait();

    if (!res) {
        UTIL_LOG(E, "player_mgr::init load player info error");
        return false;
    }

    g_console->watch_n("player_infos", [this]()->int{ return infos.size(); });
    g_console->watch_n("player_stubs", [this]()->int{ return stubs.size(); });
    g_console->watch_n("player_save_queue", [this]()->int{ return save_queue.size(); });
    g_console->watch_n("player_saving", [this]()->int{ return saving; });

    return true;
}
void player_mgr::destroy() {
	if (!g_dbf->ok())
		g_dbf->repair();

    update_save_queue();
    g_dbf->wait();

    infos.for_each([](int64_t playerid, simple_player_info_t* &info){
        safe_del(info);
    });

    stubs.for_each([](int64_t playerid, stub_t* &stub){
    	safe_del(stub);
    });

    name_table.clear();

    name_creating.clear();
}
void player_mgr::update_save_queue(int num) {
    if (num < 0)
        num = save_queue.size();

    for (int i=0; i<num; ++i) {
        if (save_queue.empty())
            break;

        auto player = save_queue.front();
        save_queue.pop();
        player->queuing = false;

        if (player->saving_count > 0) {
        	player->add_save_queue(false);
        	continue;
        }

        if (player->sav_stamp > player->chg_stamp) {
        	continue;
        }
		player->post_save_db([this, player](db_error_t err){
			if (DBE_Success != err) {
				player->add_save_queue(false);
			}
		});
    }
}

void player_mgr::update() {
	if (g_svr.status == co::ESS_Shuting) {
		update_save_queue(50);
	} else {
		update_save_queue(5);
	}

    if (stubs.size() > 10000) {
    	struct comp {
    		bool operator() (stub_t* lhs, stub_t* rhs) {
    			return lhs->load_stamp > rhs->load_stamp;
    		}
    	};

    	priority_queue<stub_t*, vector<stub_t*>, comp> gc_queue;
    	stubs.for_each([&gc_queue](int64_t playerid, stub_t* &stub){
    		if (!stub->player)
    			return;

    		if (stub->player.use_count() != 1)
    			return;

    		if (!stub->load_cb_list.empty())
    			return;

    		if (stub->create_cb)
    			return;

    		if (stub->player->queuing)
    			return;

    		if (stub->player->chg_stamp > stub->player->sav_stamp)
    			return;

    		gc_queue.push(stub);
    	});

    	while (!gc_queue.empty() && stubs.size() > 8000) {
    		auto stub = gc_queue.top();
    		gc_queue.pop();
    		stubs.erase(stub->id);
    		delete stub;
    	}
    }
}
